import { Tabs, Steps, Callout, FileTree } from "nextra/components";
import UniversalTabs from "../../components/UniversalTabs";

# Docker Compose Deployment

This guide shows how to deploy Hatchet using Docker Compose for a production-ready deployment. If you'd like to get up and running quickly, you can also deploy Hatchet using the `hatchet-lite` image following the tutorial here: [Hatchet Lite Deployment](/self-hosting/hatchet-lite).

## Quickstart

<Steps>

### Prerequisites

This deployment requires [Docker](https://docs.docker.com/engine/install/) installed locally to work.

### Create files

We will be creating 2 files in the root of your repository:

<FileTree>
  <FileTree.Folder name="root" defaultOpen>
    <FileTree.File name="docker-compose.yml" />
    <FileTree.File name="Caddyfile" />
  </FileTree.Folder>
</FileTree>

```yaml filename="docker-compose.yml" copy
version: "3.8"
services:
  postgres:
    image: postgres:15.6
    command: postgres -c 'max_connections=200'
    restart: always
    hostname: "postgres"
    environment:
      - POSTGRES_USER=hatchet
      - POSTGRES_PASSWORD=hatchet
      - POSTGRES_DB=hatchet
    ports:
      - "5435:5432"
    volumes:
      - hatchet_postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d hatchet -U hatchet"]
      interval: 10s
      timeout: 10s
      retries: 5
      start_period: 10s
  rabbitmq:
    image: "rabbitmq:3-management"
    hostname: "rabbitmq"
    ports:
      - "5673:5672" # RabbitMQ
      - "15673:15672" # Management UI
    environment:
      RABBITMQ_DEFAULT_USER: "user"
      RABBITMQ_DEFAULT_PASS: "password"
    volumes:
      - "hatchet_rabbitmq_data:/var/lib/rabbitmq"
      - "hatchet_rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf" # Configuration file mount
    healthcheck:
      test: ["CMD", "rabbitmqctl", "status"]
      interval: 10s
      timeout: 10s
      retries: 5
  migration:
    image: ghcr.io/hatchet-dev/hatchet/hatchet-migrate:latest
    environment:
      DATABASE_URL: "postgres://hatchet:hatchet@postgres:5432/hatchet"
    depends_on:
      postgres:
        condition: service_healthy
  setup-config:
    image: ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest
    command: /hatchet/hatchet-admin quickstart --skip certs --generated-config-dir /hatchet/config --overwrite=false
    environment:
      DATABASE_URL: "postgres://hatchet:hatchet@postgres:5432/hatchet"
      SERVER_TASKQUEUE_RABBITMQ_URL: amqp://user:password@rabbitmq:5672/
      SERVER_AUTH_COOKIE_DOMAIN: localhost:8080
      SERVER_AUTH_COOKIE_INSECURE: "t"
      SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
      SERVER_GRPC_INSECURE: "t"
      SERVER_GRPC_BROADCAST_ADDRESS: localhost:7077
    volumes:
      - hatchet_certs:/hatchet/certs
      - hatchet_config:/hatchet/config
    depends_on:
      migration:
        condition: service_completed_successfully
      rabbitmq:
        condition: service_healthy
      postgres:
        condition: service_healthy
  hatchet-engine:
    image: ghcr.io/hatchet-dev/hatchet/hatchet-engine:latest
    command: /hatchet/hatchet-engine --config /hatchet/config
    restart: on-failure
    depends_on:
      setup-config:
        condition: service_completed_successfully
      migration:
        condition: service_completed_successfully
    ports:
      - "7077:7070"
    environment:
      DATABASE_URL: "postgres://hatchet:hatchet@postgres:5432/hatchet"
      SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
      SERVER_GRPC_INSECURE: "t"
    volumes:
      - hatchet_certs:/hatchet/certs
      - hatchet_config:/hatchet/config
  hatchet-api:
    image: ghcr.io/hatchet-dev/hatchet/hatchet-api:latest
    command: /hatchet/hatchet-api --config /hatchet/config
    restart: on-failure
    depends_on:
      setup-config:
        condition: service_completed_successfully
      migration:
        condition: service_completed_successfully
    environment:
      DATABASE_URL: "postgres://hatchet:hatchet@postgres:5432/hatchet"
    volumes:
      - hatchet_certs:/hatchet/certs
      - hatchet_config:/hatchet/config
  hatchet-frontend:
    image: ghcr.io/hatchet-dev/hatchet/hatchet-frontend:latest
  caddy:
    image: caddy:2.7.6-alpine
    ports:
      - 8080:8080
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile

volumes:
  hatchet_postgres_data:
  hatchet_rabbitmq_data:
  hatchet_rabbitmq.conf:
  hatchet_config:
  hatchet_certs:
```

```txt filename="Caddyfile" copy
http://localhost:8080 {
	handle /api/* {
		reverse_proxy hatchet-api:8080
	}

	handle /* {
		reverse_proxy hatchet-frontend:80
	}
}
```

### Get Hatchet up and running

To start the services, run the following command in the root of your repository:

```bash
docker compose up
```

Wait for the `hatchet-engine` and `hatchet-api` services to start.

### Accessing Hatchet

Once the Hatchet instance is running, you can access the Hatchet UI at [http://localhost:8080](http://localhost:8080).

By default, a user is created with the following credentials:

```
Email: admin@example.com
Password: Admin123!!
```

### Generate a `.env` file

You can generate a `.env` file as follows:

```sh
cat <<EOF > .env
HATCHET_CLIENT_TOKEN="$(docker compose run --no-deps setup-config /hatchet/hatchet-admin token create --config /hatchet/config --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52 | xargs)"
HATCHET_CLIENT_TLS_STRATEGY=none
EOF
```

<Callout type="info" emoji="🪓">

You can also generate an API token by logging in and navigating to the "General" settings page, clicking on the "API Tokens" tab, and then clicking "Create API Token".

</Callout>

### Run your first worker

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
  <Tabs.Tab>
Make sure you have the following dependencies installed:

```sh
pip install python-dotenv
pip install hatchet-sdk
```

We are using [`python-dotenv`](https://pypi.org/project/python-dotenv/) to load the environment variables from a `.env` file. This isn't required, and you can use your own method to load environment variables.

Create a `worker.py` file with the following contents:

```python filename="worker.py" copy
from hatchet_sdk import Hatchet
from dotenv import load_dotenv

load_dotenv()

hatchet = Hatchet(debug=True)

@hatchet.workflow(name="first-python-workflow",on_events=["user:create"])
class MyWorkflow:
    @hatchet.step()
    def step1(self, context):
        return {
            "result": "success"
        }

if __name__ == "__main__":
    worker = hatchet.worker('first-worker')
    worker.register_workflow(MyWorkflow())

    worker.start()
```

Open a new terminal and start the worker with:

```sh
python3 worker.py
```

  </Tabs.Tab>
  <Tabs.Tab>
First, install `@hatchet-dev/typescript-sdk` via:

```sh npm2yarn
npm i @hatchet-dev/typescript-sdk
npm i dotenv
```

We also use `dotenv` to load the environment variables from a `.env` file. This isn't required, and you can use your own method to load environment variables.

Next, copy the following code into a `worker.ts` file:

```typescript filename="worker.ts" copy
import Hatchet, { Workflow } from "@hatchet-dev/typescript-sdk";
import dotenv from "dotenv";

dotenv.config();

const hatchet = Hatchet.init();

const workflow: Workflow = {
  id: "first-typescript-workflow",
  description: "This is my first workflow",
  on: {
    event: "user:create",
  },
  steps: [
    {
      name: "step1",
      run: async (ctx) => {
        console.log(
          "starting step1 with the following input",
          ctx.workflowInput(),
        );

        return {
          result: "success!",
        };
      },
    },
  ],
};

const worker = hatchet.worker("my-worker");
await worker.registerWorkflow(workflow);
worker.start();
```

Next, modify your `package.json` to include a script to start:

```json
{
  // ...rest of your `package.json`
  "scripts": {
    // ...existing scripts
    "worker": "npx ts-node worker.ts"
  }
}
```

Now to start the worker, in a new terminal run:

```sh npm2yarn
npm run worker
```

  </Tabs.Tab>
  <Tabs.Tab>
  In a new Go project, copy the following code into a `main.go` file:

```go filename="main.go" copy
package main

import (
	"fmt"

	"github.com/joho/godotenv"

	"github.com/hatchet-dev/hatchet/pkg/client"
	"github.com/hatchet-dev/hatchet/pkg/cmdutils"
	"github.com/hatchet-dev/hatchet/pkg/worker"
)

type stepOutput struct{}

func main() {
	err := godotenv.Load()
	if err != nil {
		panic(err)
	}

	c, err := client.New()

	if err != nil {
		panic(fmt.Sprintf("error creating client: %v", err))
	}

	w, err := worker.NewWorker(
		worker.WithClient(
			c,
		),
		worker.WithMaxRuns(1),
	)
	if err != nil {
		panic(fmt.Sprintf("error creating worker: %v", err))
	}

	err = w.RegisterWorkflow(
		&worker.WorkflowJob{
			Name:        "simple-workflow",
			Description: "Simple one-step workflow.",
      On:          worker.Events("simple"),
			Steps: []*worker.WorkflowStep{
				worker.Fn(func(ctx worker.HatchetContext) (result *stepOutput, err error) {
					fmt.Println("executed step 1")

					return &stepOutput{}, nil
				},
				).SetName("step-one"),
			},
		},
	)

	if err != nil {
		panic(fmt.Sprintf("error registering workflow: %v", err))
	}

	interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
	defer cancel()

	cleanup, err := w.Start()
	if err != nil {
		panic(fmt.Sprintf("error starting worker: %v", err))
	}

	<-interruptCtx.Done()
	if err := cleanup(); err != nil {
		panic(err)
	}
}
```

Next, run the following command to start the worker:

```sh
go run main.go
```

</Tabs.Tab>
</UniversalTabs>

### Run your first workflow

The worker is now running and listening for steps to execute. You should see your first worker registered in the `Workers` tab of the Hatchet dashboard:

![Quickstart 1](/quickstart-1.png)

You can now trigger your first workflow by navigating to the `Workflows` tab, selecting your workflow, and clicking the top right "Trigger workflow" button:

![Quickstart 2](/quickstart-2.png)

That's it! You've successfully deployed Hatchet and run your first workflow.

</Steps>

## Connecting to the engine from within Docker

If you're also running your worker application inside of `docker-compose`, you should modify the `SERVER_GRPC_BROADCAST_ADDRESS` environment variable in the `setup-config` service to use `host.docker.internal` as the hostname. For example:

```yaml
SERVER_GRPC_BROADCAST_ADDRESS: "host.docker.internal:7077"
```

<Callout type="info">
  **Note:** modifying the GRPC broadcast address or server URL will require
  re-issuing an API token.
</Callout>
